/**
 * 
 */
package org.swxjava.core;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.swxjava.assembler.MarshallException;
import org.swxjava.core.AbstractSwxMarshaller;
import org.swxjava.core.json.DefaultJsonParser;
import org.swxjava.core.json.JsonException;
import org.swxjava.core.json.JsonParser;
import org.swxjava.util.BeanUtils;
import org.swxjava.Call;
import org.swxjava.Container;
import org.swxjava.util.Log;
import org.swxjava.util.LogFactory;
import org.swxjava.util.RequestUtils;
import org.swxjava.Marshaller;
import org.swxjava.WebContext;
import org.swxjava.WebContextFactory;

public class SwxRpcMarshaller extends AbstractSwxMarshaller implements Marshaller {

	private static final Log LOG = LogFactory.getLog(SwxRpcMarshaller.class);
	
	private JsonParser parser;
	
	public SwxRpcMarshaller() {
		super();
		parser = new DefaultJsonParser();
	}

	public Call marshallInbound(HttpServletRequest request, 
				HttpServletResponse response) throws MarshallException {
		LOG.info("SWX RPC request : " + RequestUtils.createUrl(request));
		
		// Retrieve container
        WebContext webContext = WebContextFactory.get();
        Container container = webContext.getContainer();
		
		// create call object
		Call call = null;
		try {
			call = RequestUtils.createCall(request, container);
		} catch(InstantiationException e) {
			String errorMessage = "Could not instanciate service class : " + request.getParameter(RequestUtils.PARAM_SERVICE_CLASS);
			LOG.error(errorMessage);
			throw new IllegalArgumentException(errorMessage, e);
		}
		
		Object[] jsonParams = null;
		int paramLength = 0;
		// parse json args
		try {
			jsonParams = parser.parse(call.getArgs());
			if(jsonParams != null) {
				paramLength = jsonParams.length;
			}
		} catch (JsonException e) {
			LOG.error(e);
		}
		
		// Method instance
		call.setMethod(getMethod(call, paramLength));
		
		// Method parameters
		try {
			Object[] params = createParameters(call.getMethod().getParameterTypes(), jsonParams);
			call.setParameters(params);
		} catch(Exception e) {
			throw new MarshallException(call.getMethod().getParameterTypes().getClass(),e);
		}
		return call;
	}
	

	protected static Object[] createParameters(Class[] parameterTypes, Object[] params) throws Exception {
		if(parameterTypes == null || params == null) {
			return null;
		}
		for(int i=0; i < parameterTypes.length; i++) {
			Class c = parameterTypes[i];
			Object p = params[i];
			params[i] = BeanUtils.convert(c, p);
		}
		return params;
	}
	
	protected static Method getMethod(Call call, int parameterLength) {
		// ServiceClass
		Class serviceClass = call.getServiceInstance().getClass();
		List<Method> targetMethods = new ArrayList<Method>();
		Method[] ms = serviceClass.getMethods();
		for(Method m : ms) {
			if(m != null && m.getName().equals(call.getMethodName())) {
				targetMethods.add(m);
			}
		}
		
		if(targetMethods.size() == 0) {
			throw new IllegalArgumentException("Could not find the method[" + call.getMethodName() + "] of class[" + call.getServiceClass() + "]");
		}
		
		int targetParamLength = 0;
		for(Method m : targetMethods) {
			if(m != null) {
				targetParamLength = m.getParameterTypes().length;
				if(targetParamLength == parameterLength) {
					if(LOG.isDebugEnabled()) {
						LOG.debug("Method : " + m.getName() + "(" + parameterTypesToString(m.getParameterTypes()) + ")");
					}
					return m;
				}
			}
		}
		throw new IllegalArgumentException("Illegal argument length " + parameterLength + " but expected " + targetParamLength);
	}
	
	protected static String parameterTypesToString(Class[] types) {
		StringBuffer sb = new StringBuffer();
		for(int i=0; i < types.length; i++) {
			sb.append(types[i].getSimpleName());
			if(i != (types.length - 1)) {
				sb.append(", ");
			}
		}
		return sb.toString();
	}

	public void setParser(JsonParser parser) {
		this.parser = parser;
	}
	
}
